#include <string>
#include <fstream>
#include <cstring>
#include <cmath>
#include <SDL.h>

#include "config.h"
#include "audio.h"

AudioWAV::AudioWAV (const std::string &file)
{
  error = false;

  if (SDL_LoadWAV (file.c_str (), &_spec, &_buffer, &_buffer_len) == 0)
    error = true;
}

AudioWAV::~AudioWAV ()
{
  if (!error)
    SDL_FreeWAV (_buffer);
}

const SDL_AudioSpec & AudioWAV::get_spec () const
{
  return _spec;
}

unsigned int AudioWAV::get_len () const
{
  return _buffer_len;
}

unsigned int AudioWAV::copy_to_buffer (unsigned char *buffer, unsigned int num)
{
  unsigned int ncopy = num > _buffer_len - _pos ? _buffer_len - _pos : num;

  memcpy (buffer, _buffer, ncopy);
  _pos += ncopy;

  return ncopy;
}

bool AudioWAV::operator! () const
{
  return error;
}

const unsigned char & AudioWAV::operator[] (unsigned int idx) const
{
  if (idx < 0)
    idx = 0;
  else if (idx >= _buffer_len)
    idx = _buffer_len - 1;

  return _buffer[idx];
}



AudioParams::AudioParams (const std::string &wav_file)
  : _tap (wav_file)
{
  _bpm = 0;
  _beats = 0;
  _accent_vol = 0;
  _quarter_vol = 0;
  _eight_vol = 0;
  _sixteen_vol = 0;
  _triplet_vol = 0;
  _master_vol = 0;
}

AudioParams::AudioParams (const std::string &wav_file,
                          const SDL_AudioSpec &spec)
  : _tap (wav_file)
{
  memcpy (&_spec, &spec, sizeof (_spec));

  _bpm = 0;
  _beats = 0;
  _accent_vol = 0;
  _quarter_vol = 0;
  _eight_vol = 0;
  _sixteen_vol = 0;
  _triplet_vol = 0;
  _master_vol = 0;
}

void AudioParams::set_volume (Volume vol, int value)
{
  SDL_LockAudio ();

  switch (vol) {
    case Accent: _accent_vol = value; break;
    case Quarter: _quarter_vol = value; break;
    case Eight: _eight_vol = value; break;
    case Sixteen: _sixteen_vol = value; break;
    case Triplet: _triplet_vol = value; break;
    case Master: _master_vol = value; break;
    default: break;
  }

  SDL_UnlockAudio ();
}

void AudioParams::set_bpm (int bpm)
{
  SDL_LockAudio ();
  _bpm = bpm;
  SDL_UnlockAudio ();
}

void AudioParams::set_beats (int beats)
{
  SDL_LockAudio ();
  _beats = beats;
  SDL_UnlockAudio ();
}

void AudioParams::set_spec (const SDL_AudioSpec &spec)
{
  memcpy (&_spec, &spec, sizeof (_spec));
}

int AudioParams::get_volume (Volume type) const
{
  switch (type) {
    case Accent: return _accent_vol;
    case Quarter: return _quarter_vol;
    case Eight: return _eight_vol;
    case Sixteen: return _sixteen_vol;
    case Triplet: return _triplet_vol;
    case Master: return _master_vol;
    default: break;
  }

  return 0;
}

int AudioParams::get_bpm () const
{
  return _bpm;
}

int AudioParams::get_beats () const
{
  return _beats;
}

const AudioWAV & AudioParams::get_audio_wav () const
{
  return _tap;
}

const SDL_AudioSpec & AudioParams::get_spec () const
{
  return _spec;
}

void AudioParams::read_settings (const std::string &file)
{
  std::ifstream settings (file.c_str ());
  int tmp;

  if (settings.is_open ()) {
    settings >> tmp;
    _bpm = tmp;
    settings >> tmp;
    _beats = tmp;
    settings >> tmp;
    _accent_vol = tmp;
    settings >> tmp;
    _quarter_vol = tmp;
    settings >> tmp;
    _eight_vol = tmp;
    settings >> tmp;
    _sixteen_vol = tmp;
    settings >> tmp;
    _triplet_vol = tmp;
    settings >> tmp;
    _master_vol = tmp;
  }

  settings.close ();
}

void AudioParams::write_settings (const std::string &file)
{
  std::ofstream settings (file.c_str (), std::ios::trunc);

  if (!settings.is_open ())
    return;

  settings << _bpm << std::endl;
  settings << _beats << std::endl;
  settings << _accent_vol << std::endl;
  settings << _quarter_vol << std::endl;
  settings << _eight_vol << std::endl;
  settings << _sixteen_vol << std::endl;
  settings << _triplet_vol << std::endl;
  settings << _master_vol << std::endl;

  settings.close ();
}



void audio_callback (void *data, unsigned char *stream, int len)
{
  AudioParams *params = (AudioParams *)data;
  const SDL_AudioSpec &spec = params->get_spec ();
  const AudioWAV &mwav = params->get_audio_wav ();
  const SDL_AudioSpec &mwav_spec = mwav.get_spec ();

  static int beat_counter = 0;
  /* Period of a beat at current BPM */
  static int beat_counter_period = 0;
  static long beat_counter_time = 0;
  static int beats = 0;
  static int beat_vol = -30;
  static unsigned int sound_index = 0;
  static int last_bpm = 0;
  /* Amount of time for one sample from stream */
  static long stream_period = 0;

  if (stream_period == 0)
    stream_period = (long)(1.0 / (float)(spec.freq) * 1000000000);

  if (last_bpm != params->get_bpm ()) {
    beat_counter_period = (int)(60.0 / (float)(params->get_bpm () * 12)
                                * 1000000000);
    sound_index = 0;
    beat_counter = 0;
    beats = 0;
    last_bpm = params->get_bpm ();
  }

  for (int i = 0; i < len; i++) {
    switch (beat_counter) {
      case 0:
        if(beats == 0)
          beat_vol = params->get_volume (Accent)
                     + params->get_volume (Quarter);
        else
          beat_vol = params->get_volume (Quarter);

        break;
      case 6:
        beat_vol = params->get_volume (Eight);
        break;
      case 3:
      case 9:
        beat_vol = params->get_volume (Sixteen);
        break;
      case 4:
      case 8:
        beat_vol = params->get_volume (Triplet);
    }

    if (sound_index < mwav.get_len ()) {
      double mvol;
      if (params->get_volume (Master) <= -30)
        mvol = 0;
      else
        mvol = exp (params->get_volume (Master) / 10.0);

      double bvol;
      if (beat_vol <= -30)
        bvol = 0;
      else
        bvol = exp (beat_vol / 10.0);

      stream[i] = (char)((mwav[sound_index++] - spec.silence) * mvol * bvol
                         + spec.silence);
    }

    beat_counter_time += stream_period;

    if (beat_counter_time >= beat_counter_period) {
      beat_counter_time = 0;
      beat_counter = (beat_counter + 1) % 12;

      if(beat_counter == 0)
        beats = (beats + 1) % params->get_beats ();

      if(beat_counter % 3 == 0 || beat_counter % 4 == 0)
        sound_index = 0;
    }
  }
}
